/********************************************************************
 * Filename:	FileSystemManager.c
 * Description: The file system manager.
 * Group 2:		nachOS
 * Author(s):	Daniel Fairweather
 *              Ying Wang
 * Class:		CSE 325
 * Instructor:	David Duggan
 * Assignment:	Lab #8 File System Implementation
 * Assigned:	4/16/2013
 * Due:			4/30/2013
 ********************************************************************/

#include "FileSystemManager.h"

int device_list[1] = {0};   /* The devices which are currently initialized. */
int current_directory = 0;  /* The current directory. */
int files[100][2] = {0};    /* The files which are currently open. R = 1, RW = 2, New = 0. */
int currently_mounted = -1; /* The Device ID of the currently mounted fs. */
int buffers[10] = {0};  /* The buffer array. */

int init_device(int port){
    /* If the port number is in range, try to init it. */
    if(port == 0){
		/* If the device was already formatted. */
		if(device_list[port] == 2){
			return(0);
		}
        /* If the device hasn't already been initialized, do so. */
        if(device_list[port] != 1){
            device_list[port] = 1;
            return(0);
        }
        /* If the device has been initialized, return an error. */
        return(-1);
    }
    return(-2);
}


int format_fs(int port, char* fs_name, int blocksize){
    if(port == 0){
        /* If the block has been initalized, continue with formatting. */
        if(device_list[port] == 1 || device_list[port] == 2){
            /* If the blocksize is in the correct range, format it. */
            if(blocksize >= 1 && blocksize <= 4){
                FSIB fsib;
                FCB root;

                /* Setup FSIB */
                format_device(blocksize * 1024);
                fsib.block_size = blocksize - 1;
                string_copy(fs_name, fsib.fs_name);
                fsib.max_block = (DEVICE_SIZE/1024)/blocksize;
                fsib.root_block = get_free_block();
                write_FSIB(&fsib);

                /* Setup ROOT directory */
                string_copy("", root.filename);
                root.filesize = 0;
                root.isDirectory = 1;
                write_FCB(&root, (SEGTYPE)fsib.root_block);
                set_block_allocation((SEGTYPE)fsib.root_block, 1);
                add_address((SEGTYPE)fsib.root_block, (SEGTYPE)fsib.root_block);

				device_list[port] = 2;

                return(0);
            }
            /* If the blocksize is out of range, return an error. */
            else{
                return(-3);
            }
        }
        /* If the port has not been initialized, return an error. */
        else{
            return(-2);
        }
    }
    /* If the port number is out of range, return an error. */
    else{
        return(-1);
    }
}


int create_file(char* filename, int isDirectory){
    FCB cwd;
    FCB fcb;
    int free_index;
    int i = 0;

    /* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

    /* Check if the file contains illegal characters. */
    while(1){
        if(filename[i] == '/'){
            return(-2);
        }
        else if(filename[i] == '\0'){
            break;
        }
        i++;
    }

    /* Set the properties of the file. */
    string_copy(filename, fcb.filename);
    fcb.filesize = 0;
    if(isDirectory == 0){
        fcb.isDirectory = 0;
    }
    else{
        fcb.isDirectory = 1;
    }

    /* Get an available index from the device. */
    free_index = get_free_block();
    if(free_index == 0){
        return(-1);
    }

    /* Write the block to the device. */
    write_FCB(&fcb, (SEGTYPE)free_index);
    set_block_allocation((SEGTYPE)free_index, 1);

    /* Add a refrence to the new block in the current directory and update the filesize. */
    add_address((SEGTYPE)current_directory, (SEGTYPE)free_index);
    read_FCB(&cwd, (SEGTYPE)current_directory);
    cwd.filesize++;
    write_FCB(&cwd, (SEGTYPE)current_directory);

    if(fcb.isDirectory == 1){
        add_address((SEGTYPE)free_index, (SEGTYPE)current_directory);
    }

    return(free_index);
}


int file_exists(char* filename){
    FCB fcb;
    int i = 1;
    int block;

    while(1){
        block = read_address((SEGTYPE)current_directory, i);
        if(block == 0){
            return(-1);
        }
        read_FCB(&fcb, (SEGTYPE)block);
        if(string_cmp(filename, fcb.filename) == 0 && fcb.isDirectory == 0){
            return(block);
        }
        i++;
    }
}


int directory_exists(char* directoryname){
    FCB fcb;
    int i = 1;
    int block;

    while(1){
        block = read_address((SEGTYPE)current_directory, i);
        if(block == 0){
            return(-1);
        }
        read_FCB(&fcb, (SEGTYPE)block);
        if(string_cmp(directoryname, fcb.filename) == 0 && fcb.isDirectory == 1){
            return(block);
        }
        i++;
    }
}


int mount_fs(char* fs_name){
	FSIB fs;
	if(device_list[0] != 2){
		return(-3);
	}
	if(currently_mounted == -1){
		read_FSIB(&fs);
		if(string_cmp(fs.fs_name, fs_name) == 0){
			currently_mounted = 0;
            current_directory = fs.root_block;
			return(0);
		}
		else{
			return(-2);
		}
	}
	else{
		return(-1);
	}
}


int unmount_fs(char* fs_name){
	FSIB temp;
	if(currently_mounted != -1){
		/* Check if the specifies fs is currently mounted. */
		read_FSIB(&temp);
		if(string_cmp(temp.fs_name, fs_name) == 0 && currently_mounted == 0){
			currently_mounted = -1;
			current_directory = 0;
			close_open_files();
			return(0);
		}
		/* If the currently mounted is not device 0. */
		else{
			return(-2);
		}
	}
	/* Nothing was mounted. */
	else{
		return(-1);
	}
}


int string_cmp(char* string1, char* string2){
	int i = 0;
	while(string1[i] != '\0' && string2[i] != '\0'){
		/* If the characters don't match, errors. */
		if(string1[i] != string2[i]){
			return(-1);
		}
		/* Move to the next character. */
		else{
			i++;
		}
	}
	/* Check if both strings reach the end. */
	if(string1[i] == '\0' && string2[i] == '\0'){
		return(0);
	}
	/* Only one string ends. Difference in string length, errors. */
	else{
		return(-1);
	}
}

void string_copy(char* source, char* destination){
    int i = 0;
    while(source[i] != '\0'){
        destination[i] = source[i];
        i++;
    }
    destination[i] = '\0';
}


int get_free_filehandle(){
	int i = 0;
	for(i = 0; i < 100; i++){
		/* Returns the index of the first available spot. */
		if(files[i][0] == 0){
			return(i);
		}
	}
	/* Errors if there's no availabel spot in fileHandles. */
	if(i == 100){
		return(-1);
	}
}


int open_file(char* filename, int mode){
	int blockNumber = file_exists(filename);
	int filehandle = get_free_filehandle();

    /* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

	/* Needs to check if the file exists. */
	if(blockNumber != -1){
		if(filehandle != -1){
			files[filehandle][0] = blockNumber;
			files[filehandle][1] = mode;
			return filehandle;
		}
		else{
			return(-2);
		}
	}
	else{
		return(-1);
	}
}


int read_file(int filehandle, int blocknumber, int buffer){


    /* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

	/* Check if the file handle is valid. */
	if(files[filehandle][0] == 0){
		return(-4);
	}

    /* Check if the file is in the read mode. */

	if(files[filehandle][1] == 1 || files[filehandle][1] == 2){
		/* Check if the buffer address is valid. */
	    if(buffer < 0 || buffer > 9){
			return(-3);
		}
		/* Check if the blocknumber is in the file. */
		if(has_block(files[filehandle][0], blocknumber) == 0){
			// Write the content of that block to the buffer
			return(0);
		}
		else{
			return(-1);
		}
	}
	else{
		return(-2);
	}
}


int write_file(int filehandle, int blocknumber, int buffer){
	FCB temp;
	FSIB fs_info;

    /* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

	/* Check if the file handle is valid. */
	if(files[filehandle][0] == 0){
		return(-4);
	}

	read_FSIB(&fs_info);
	if(blocknumber <= fs_info.root_block){
		return(-6);
	}

	/* Check if the file is in the write mode. */
	if(files[filehandle][1] == 2){
		/* Check if the buffer address is valid. */
	    if(buffer < 0 || buffer > 9){
			return(-3);
		}
		/* Check if the blocknumber is in the file. */
		if(has_block(files[filehandle][0], blocknumber) == 0){
			// Write the content of the buffer to the block
			return(0);
		}
		else{
			/* Check if the blocknumber is used by other files. */
			if(get_block_allocation((SEGTYPE)blocknumber) == 0){
				// Write the content of the buffer to the block
				if(add_address((SEGTYPE)files[filehandle][0], blocknumber) == 1){
					/* Update the FCB. */
					read_FCB(&temp, (SEGTYPE)files[filehandle][0]);
					temp.filesize++;
					write_FCB(&temp, (SEGTYPE)files[filehandle][0]);
					return(1);
				}
				else{
					return(-5);
				}
			}
			else{
				return(-1);
			}
		}
	}
	else{
		return(-2);
	}
}


int close_file(int filehandle){
    /* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

	if(files[filehandle][0] == 0){
		return(-1);
	}
	/* Resets the mode and the FCB blocknumber. */
	else{
		files[filehandle][0] = 0;
		files[filehandle][1] = 0;
		return 0;
	}
}


void close_open_files(){
	int i = 0;
	for(i = 0; i < 100; i++){
		files[i][0] = 0;
		files[i][1] = 0;
	}
}


int delete_directory(char* directory_name){
    FCB fcb;
    FCB temp;
    int block;
    int i = 1;
    int result;
	int parent_Directory;
	int blocknumber;

    /* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

    block = directory_exists(directory_name);
    if(block == -1){
        return(-1);
    }

    /* Loop through all indexes in the current directory. */
    while(1){
        result = read_address((SEGTYPE)block, i);

        /* If the block number returned is valid, get the FCB and print it. */
        if(result != 0){
            read_FCB(&fcb, result);
            /* If the file is not a directory, delete it. */
            if(fcb.isDirectory == 0){
                delete_file(fcb.filename);
                
            }
            /* If the file is a directory, delete it's children first. */
            else{
                current_directory = block;
                delete_directory(fcb.filename);
                current_directory = read_address((SEGTYPE)block, 0);
                set_block_allocation((SEGTYPE)result, 0);
                set_address((SEGTYPE)block,i,0);
            }
            i++;
        }
        /* If the block number is invalid, we have reached then end of the */
        else{
            break;
        }
    }

    return(0);
}



int delete_file(char* filename){
	FCB temp;
	int blocknumber;
	int i = 0;
	int result; 
	int block;

	/* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

    block = directory_exists(filename);
    /* If this is a directory, just delete it. */
    if(block != -1){
        result = delete_directory(filename);
        if(result == 0){
            set_block_allocation((SEGTYPE)block, 0);


		    /* Remove the entry from the directory. */
		    i = 1;
		    while(1){
			    blocknumber = read_address((SEGTYPE)current_directory, i);
			    if(blocknumber == 0){
				    return(-1);
			    }
			    if(block == blocknumber){
				    set_address(current_directory, i, 0);
				    break;
			    }
			    else{
				    i++;
			    }
		    }

		    /* Update the FCB. */
		    read_FCB(&temp, (SEGTYPE)current_directory);
		    set_address((SEGTYPE)current_directory, i, read_address((SEGTYPE)current_directory, temp.filesize));
		    set_address((SEGTYPE)current_directory, temp.filesize, 0);
		    temp.filesize--;
		    write_FCB(&temp, (SEGTYPE)current_directory);
		    return(0);
        }
        return(result);
    }


	blocknumber = file_exists(filename);
	if(blocknumber != -1){
		/* Get all the blocks in this FCB and mark them as unused. */
		while(1){
			result = read_address((SEGTYPE)blocknumber, i);
			if(result != 0){
				set_address((SEGTYPE)blocknumber, i, 0);
				set_block_allocation((SEGTYPE)result, 0);
				i++;
			}
			else{
				break;
			}
		}

		set_block_allocation((SEGTYPE)blocknumber, 0);

		/* Remove the entry from the directory. */
		i = 1;
		while(1){
			block = read_address((SEGTYPE)current_directory, i);
			if(block == 0){
				return(-1);
			}
			if(block == blocknumber){
				set_address(current_directory, i, 0);
				break;
			}
			else{
				i++;
			}
		}

		/* Update the FCB. */
		read_FCB(&temp, (SEGTYPE)current_directory);
		set_address((SEGTYPE)current_directory, i, read_address((SEGTYPE)current_directory, temp.filesize));
		set_address((SEGTYPE)current_directory, temp.filesize, 0);
		temp.filesize--;
		write_FCB(&temp, (SEGTYPE)current_directory);
		return(0);
	}
	else{
		return(-1);
	}	
}


int has_block(int fcb_number, int block_number){
	int result;
	int i = 0;
	if(block_number == 0){
		return(-1);
	}

	while(1){
		result = read_address((SEGTYPE)fcb_number, i);
		if(result == block_number){
			return(0);
		}
		if(result != 0){
			i++;
		}
		else{
			break;
		}
	}
	return(-1);
}


int cd_directory(char* directory){
	int block_number;

	/* Check if a file system has been mounted. */
    if(currently_mounted == -1){
        return(-99);
    }

	block_number = directory_exists(directory);
	if(block_number != -1){
		current_directory = block_number;
		return(0);
	}
	else{
		return(-1);
	}
}